#include "internal.h"

static inline void replenish_dl_new_period(struct sched_dl_entity *dl_se,
                                           struct rq *rq)
{
    /* for non-boosted task, pi_of(dl_se) == dl_se */
    dl_se->deadline = rq_clock(rq) + pi_of(dl_se)->dl_deadline;
    dl_se->runtime = pi_of(dl_se)->dl_runtime;

    /*
     * If it is a deferred reservation, and the server
     * is not handling an starvation case, defer it.
     */
    if (dl_se->dl_defer && !dl_se->dl_defer_running)
    {
        dl_se->dl_throttled = 1;
        dl_se->dl_defer_armed = 1;
    }
}

/*
 * Pure Earliest Deadline First (EDF) scheduling does not deal with the
 * possibility of a entity lasting more than what it declared, and thus
 * exhausting its runtime.
 *
 * Here we are interested in making runtime overrun possible, but we do
 * not want a entity which is misbehaving to affect the scheduling of all
 * other entities.
 * Therefore, a budgeting strategy called Constant Bandwidth Server (CBS)
 * is used, in order to confine each entity within its own bandwidth.
 *
 * This function deals exactly with that, and ensures that when the runtime
 * of a entity is replenished, its deadline is also postponed. That ensures
 * the overrunning entity can't interfere with other entity in the system and
 * can't make them miss their deadlines. Reasons why this kind of overruns
 * could happen are, typically, a entity voluntarily trying to overcome its
 * runtime, or it just underestimated it during sched_setattr().
 */
static void replenish_dl_entity(struct sched_dl_entity *dl_se)
{
    struct dl_rq *dl_rq = dl_rq_of_se(dl_se);
    struct rq *rq = rq_of_dl_rq(dl_rq);

    WARN_ON_ONCE(pi_of(dl_se)->dl_runtime <= 0);

    /*
     * This could be the case for a !-dl task that is boosted.
     * Just go with full inherited parameters.
     *
     * Or, it could be the case of a deferred reservation that
     * was not able to consume its runtime in background and
     * reached this point with current u > U.
     *
     * In both cases, set a new period.
     */
    if (dl_se->dl_deadline == 0 ||
        (dl_se->dl_defer_armed && dl_entity_overflow(dl_se, rq_clock(rq))))
    {
        dl_se->deadline = rq_clock(rq) + pi_of(dl_se)->dl_deadline;
        dl_se->runtime = pi_of(dl_se)->dl_runtime;
    }

    if (dl_se->dl_yielded && dl_se->runtime > 0)
        dl_se->runtime = 0;

    /*
     * We keep moving the deadline away until we get some
     * available runtime for the entity. This ensures correct
     * handling of situations where the runtime overrun is
     * arbitrary large.
     */
    while (dl_se->runtime <= 0)
    {
        dl_se->deadline += pi_of(dl_se)->dl_period;
        dl_se->runtime += pi_of(dl_se)->dl_runtime;
    }

    /*
     * At this point, the deadline really should be "in
     * the future" with respect to rq->clock. If it's
     * not, we are, for some reason, lagging too much!
     * Anyway, after having warn userspace abut that,
     * we still try to keep the things running by
     * resetting the deadline and the budget of the
     * entity.
     */
    if (dl_time_before(dl_se->deadline, rq_clock(rq)))
    {
        printk_deferred_once("sched: DL replenish lagged too much\n");
        replenish_dl_new_period(dl_se, rq);
    }

    if (dl_se->dl_yielded)
        dl_se->dl_yielded = 0;
    if (dl_se->dl_throttled)
        dl_se->dl_throttled = 0;

    /*
     * If this is the replenishment of a deferred reservation,
     * clear the flag and return.
     */
    if (dl_se->dl_defer_armed)
    {
        dl_se->dl_defer_armed = 0;
        return;
    }

    /*
     * A this point, if the deferred server is not armed, and the deadline
     * is in the future, if it is not running already, throttle the server
     * and arm the defer timer.
     */
    if (dl_se->dl_defer && !dl_se->dl_defer_running &&
        dl_time_before(rq_clock(dl_se->rq), dl_se->deadline - dl_se->runtime))
    {
        if (!is_dl_boosted(dl_se) && dl_se->server_has_tasks(dl_se))
        {

            /*
             * Set dl_se->dl_defer_armed and dl_throttled variables to
             * inform the start_dl_timer() that this is a deferred
             * activation.
             */
            dl_se->dl_defer_armed = 1;
            dl_se->dl_throttled = 1;
            if (!start_dl_timer(dl_se))
            {
                /*
                 * If for whatever reason (delays), a previous timer was
                 * queued but not serviced, cancel it and clean the
                 * deferrable server variables intended for start_dl_timer().
                 */
                hrtimer_try_to_cancel(&dl_se->dl_timer);
                dl_se->dl_defer_armed = 0;
                dl_se->dl_throttled = 0;
            }
        }
    }
}
